package com.zHan.system.filter;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zHan.common.result.Result;
import com.zHan.common.result.ResultCodeEnum;
import com.zHan.common.utils.IpUtil;
import com.zHan.common.utils.JwtUtil;
import com.zHan.common.utils.ResponseUtil;
import com.zHan.model.constant.RedisConstant;
import com.zHan.model.dto.LoginDto;
import com.zHan.model.system.SysUser;
import com.zHan.system.custom.CustomUser;
import com.zHan.system.service.ISysLoginLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 登录过滤器，继承UsernamePasswordAuthenticationFilter，对用户名密码进行登录校验
 * </p>
 */
@Slf4j
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private StringRedisTemplate stringRedisTemplate;

    private ISysLoginLogService loginLogService;

    /**
     * 构造
     *
     * @param authenticationManager
     */
    public TokenLoginFilter(AuthenticationManager authenticationManager,
                            StringRedisTemplate stringRedisTemplate,
                            ISysLoginLogService loginLogService) {
        this.setAuthenticationManager(authenticationManager);
        this.setPostOnly(false);
        //指定登录接口及提交方式，可以指定任意路径
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system" +
                "/index/login", "POST"));
        this.stringRedisTemplate = stringRedisTemplate;
        this.loginLogService = loginLogService;
    }

    /**
     * 获取用户名和密码，登录认证
     *
     * @param req
     * @param res
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            LoginDto loginDto = new ObjectMapper().readValue(req.getInputStream(), LoginDto.class);

            Authentication authenticationToken =
                    new UsernamePasswordAuthenticationToken(loginDto.getUsername(),
                            loginDto.getPassword());
            return this.getAuthenticationManager().authenticate(authenticationToken);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 登录成功
     *
     * @param request
     * @param response
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response, FilterChain chain,
                                            Authentication auth) throws IOException,
            ServletException {
        CustomUser customUser = (CustomUser) auth.getPrincipal();

        // 保存权限数据
        String authKey = RedisConstant.AUTHORITY_KEY + customUser.getUsername();
        stringRedisTemplate.opsForValue().set(authKey,
                JSON.toJSONString(customUser.getAuthorities()));

        // 获取user对象
        SysUser sysUser = customUser.getSysUser();
        // 生成token
        String token = JwtUtil.createToken(sysUser.getId(),
                sysUser.getUsername());

        Map<String, Object> map = new HashMap<>();
        map.put("token", token);

        // 把token存储到redis中
        String tokenKey = RedisConstant.TOKEN_KEY + token;
        stringRedisTemplate.opsForValue().set(tokenKey, token, 1, TimeUnit.HOURS);

        // 添加登录日志
        String ipAddr = getIpAddr(request);
        loginLogService.recordLoginLog(sysUser.getUsername(), 1, ipAddr,
                "登录成功");

        ResponseUtil.out(response, Result.ok(map));
    }

    /**
     * 登录失败
     *
     * @param request
     * @param response
     * @param e
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response,
                                              AuthenticationException e) throws IOException,
            ServletException {

        // 获取ip
        String ipAddr = getIpAddr(request);
        if (e.getCause() instanceof RuntimeException) {
            ResponseUtil.out(response, Result.build(null, 204, e.getMessage()));

            // 添加登录日志
            loginLogService.recordLoginLog("未知", 0, ipAddr, e.getMessage());
        } else {
            ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_MOBLE_ERROR));
            // 添加登录日志
            loginLogService.recordLoginLog("未知", 0, ipAddr,
                    ResultCodeEnum.LOGIN_MOBLE_ERROR.getMessage());
        }
    }

    /**
     * 获取ip
     *
     * @param request
     * @return
     */
    private String getIpAddr(HttpServletRequest request) {
        String ipAddr = IpUtil.getIpAddress(request);
        if ("0:0:0:0:0:0:0:1".equals(ipAddr)) {
            ipAddr = "127.0.0.1";
        }
        return ipAddr;
    }
}